//extern crate tantivy_jieba;

use search::*;
use tantivy::collector::TopDocs;
use tantivy::query::QueryParser;
use tantivy::schema::*;
use tantivy::{doc, ReloadPolicy,Snippet, Index, SnippetGenerator};
use tempfile::TempDir;
use tantivy::Score;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tantivy_jieba;
use gfsc::*;

pub type Result<T> = anyhow::Result<T>;

#[derive(Serialize)]
struct Serp {
    q: String,
    num_hits: usize,
    hits: Vec<Hit>
   // timings: TimerTree,
}

#[derive(Serialize,Debug)]
struct Hit {
    score: Score,
    doc: NamedFieldDocument,
    id: u32,
}
  
#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}

#[test]
pub fn test_chinese_search(){
    // Let's create a temporary directory for the
    // sake of this example
    let index_path = TempDir::new().unwrap();

    // # Defining the schema
    // First we need to define a schema ...    
    let tokenizer = tantivy_jieba::JiebaTokenizer {};    
    let text_indexing = TextFieldIndexing::default()
        .set_tokenizer("jieba") // Set custom tokenizer
        .set_index_option(IndexRecordOption::WithFreqsAndPositions);
    let text_options = TextOptions::default()
        .set_indexing_options(text_indexing)
        .set_stored();

    let mut schema_builder = Schema::builder();
    schema_builder.add_text_field("title", text_options.clone());
    schema_builder.add_text_field("body", text_options.clone());
    let schema = schema_builder.build();
    
    let index = Index::create_in_dir(&index_path, schema.clone()).unwrap();
    index.tokenizers().register("jieba", tokenizer);

    let mut index_writer = index.writer(50_000_000).unwrap();

    let title = schema.get_field("title").unwrap();
    let body = schema.get_field("body").unwrap();

    let mut old_man_doc = Document::default();
    old_man_doc.add_text(title, "企业搜索服务器");
    old_man_doc.add_text(body,
        "全文本搜索库。Solr：一个具有强大REST API的企业搜索服务器。Nutch：一个依靠Apache Hadoop的可扩展和可伸缩的网络爬行器。由于Lucene是许多开源或闭源搜索引擎背后的技术，它被认为是参考的搜索库。",
    );
    // ... and add it to the `IndexWriter`.
    index_writer.add_document(old_man_doc).unwrap();
// For convenience, tantivy also comes with a macro to    
    index_writer.add_document(doc!(
        title => "一个带有列文斯坦自动机的倒置索引组成",
        body => "Sonic是一个用Rust编写的轻量级和无模式的搜索索引服务器。Sonic不能被认为是一个开箱即用的解决方案，与MeiliSearch相比，它不能保证相关性排名。"
        )).unwrap();

    // Multivalued field just need to be repeated.
    index_writer.add_document(doc!(
        title => "Frankenstein",
        title => "Solr是Apache Lucene的一个子项目",
        body => "由Yonik Seeley于2004年创建，如今是全球范围内使用最广泛的搜索引擎之一。Solr是一个搜索平台，用Java编写，并建立在Lucene之上服务器"
        )).unwrap();

    index_writer.commit().unwrap();

    let reader = index
        .reader_builder()
        .reload_policy(ReloadPolicy::OnCommit)
        .try_into().unwrap();

    let searcher = reader.searcher();
    
    let query_parser = QueryParser::for_index(&index, vec![title, body]);
    // `QueryParser` may fail if the query is not in the right
    // format. For user facing applications, this can be a problem.
    let query = query_parser.parse_query("搜索").unwrap();
    // We can now perform our query.
   
    let top_docs = searcher.search(&query, &TopDocs::with_limit(10)).unwrap();
    let snippet_generator = SnippetGenerator::create(&searcher, &*query, body).unwrap();

    for (score, doc_address) in top_docs {
        let doc = searcher.doc(doc_address).unwrap();
        let snippet = snippet_generator.snippet_from_doc(&doc);
        println!("Document score {}:", score);
        println!(
            "title: {}",
            doc.get_first(title).unwrap().as_text().unwrap()
        );        
        // println!("snippets: {}", snippet.to_html());        
        println!("custom highlighting: {}", highlight(&snippet));
        //println!("custom highlighting: {}", highlight_color(&snippet));
    }     
/* 
    println!("return {} items!", top_docs.len());
    let hits: Vec<Hit> = {    
        top_docs
        .iter()
        .map(|(scor, doc_address)| {
            let doc: Document = searcher.doc(*doc_address).unwrap();
            Hit {
                score: *scor,
                doc: schema.to_named_doc(&doc),
                id: doc_address.doc_id,
            }
        })
        .collect()
    };
    println!("{:#?}", hits);
    */
}

#[test]
pub fn test_datetime_search() {
    // println!("{}, {}",tantivy::version_string(), Colour::Red.paint("0.18.0及以下版本不支持DateOptions！！！，需要等待下个版本！"));
    assert!(tantivy::version_string().contains("0.18.0"));
    /* 
    let index_path = TempDir::new().unwrap();
      // # Defining the schema
      let mut schema_builder = Schema::builder();
      
      let opts =   DateOptions::from(INDEXED)
          .set_stored()
          .set_fast(Cardinality::SingleValue)
          .set_precision(tantivy::DatePrecision::Seconds);
      let occurred_at = schema_builder.add_date_field("occurred_at", opts);
      let event_type = schema_builder.add_text_field("event", STRING | STORED);
      let schema = schema_builder.build();
  
      // # Indexing documents
      let index = Index::create_in_ram(schema.clone());
  
      let mut index_writer = index.writer(50_000_000).unwrap();
      let doc = schema.parse_document(
          r#"{
          "occurred_at": "2022-06-22T12:53:50.53Z",
          "event": "pull-request"
      }"#,
      ).unwrap();
      index_writer.add_document(doc).unwrap();
      let doc = schema.parse_document(
          r#"{
          "occurred_at": "2022-06-22T13:00:00.22Z",
          "event": "comment"
      }"#,
      ).unwrap();
      index_writer.add_document(doc).unwrap();
      index_writer.commit().unwrap();
  
      let reader = index.reader().unwrap();
      let searcher = reader.searcher();
  
      // # Default fields: event_type
      let query_parser = QueryParser::for_index(&index, vec![event_type]);
      {
          let query = query_parser.parse_query("event:comment").unwrap();
          let count_docs = searcher.search(&*query, &TopDocs::with_limit(5)).unwrap();
          assert_eq!(count_docs.len(), 1);
      }
      {
          let query = query_parser
              .parse_query(r#"occurred_at:[2022-06-22T12:58:00Z TO 2022-06-23T00:00:00Z}"#).unwrap();
          let count_docs = searcher.search(&*query, &TopDocs::with_limit(4)).unwrap();
          assert_eq!(count_docs.len(), 1);
          for (_score, doc_address) in count_docs {
              let retrieved_doc = searcher.doc(doc_address)?;
              assert!(matches!(
                  retrieved_doc.get_first(occurred_at),
                  Some(Value::Date(_))
              ));
              assert_eq!(
                  schema.to_json(&retrieved_doc),
                  r#"{"event":["comment"],"occurred_at":["2022-06-22T13:00:00.22Z"]}"#
              );
          }
      }    
      */
}
      

#[test]
fn test_serde_json(){    
    // Some JSON input data as a &str. Maybe this comes from the user.
    let data = r#"
        {
            "name": "<mark>John Doe</mark>",
            "age": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }"#;

    // Parse the string of data into a Person object. This is exactly the
    // same function as the one that produced serde_json::Value above, but
    // now we are asking it for a Person as output.
    let p: Person = serde_json::from_str(data).unwrap();
    // Do things just like with any other Rust data structure.
    println!("Please call at the number {:#?}", p);
}

#[test]
fn test_serde_to_json(){    
        // The type of `john` is `serde_json::Value`
        let john = json!({
            "name": "<mark>John Doe<mark>",
            "age": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        });
    
        println!("first phone number: {}", john["phones"][0]);
        println!("{}", john.to_string());    
}

#[test]
fn test_snippet_highline(){
    // Let's create a temporary directory for the
    // sake of this example
    let index_path = TempDir::new().unwrap();

    // # Defining the schema
    let mut schema_builder = Schema::builder();
    let title = schema_builder.add_text_field("title", TEXT | STORED);
    let body = schema_builder.add_text_field("body", TEXT | STORED);
    let schema = schema_builder.build();

    // # Indexing documents
    let index = Index::create_in_dir(&index_path, schema).unwrap();

    let mut index_writer = index.writer(50_000_000).unwrap();

    // we'll only need one doc for this example.
    index_writer.add_document(doc!(
    title => "Of Mice and Men",
    body => "A few miles south of Soledad, the Salinas River drops in close to the hillside \
            bank and runs deep and green. The water is warm too, for it has slipped twinkling \
            over the yellow sands in the sunlight before reaching the narrow pool. On one \
            side of the river the golden foothill slopes curve up to the strong and rocky \
            Gabilan Mountains, but on the valley side the water is lined with trees—willows \
            fresh and green with every spring, carrying in River lower leaf junctures the \
            debris of the winter’s flooding; and sycamores with mottled, white, recumbent \
            limbs and branches that River over the pool"
    )).unwrap();
    // ...
    index_writer.commit().unwrap();

    let reader = index.reader().unwrap();
    let searcher = reader.searcher();
    let query_parser = QueryParser::for_index(&index, vec![title, body]);
    let query = query_parser.parse_query("River").unwrap();

    let top_docs = searcher.search(&query, &TopDocs::with_limit(10)).unwrap();
    let snippet_generator = SnippetGenerator::create(&searcher, &*query, body).unwrap();

    for (score, doc_address) in top_docs {
        let doc = searcher.doc(doc_address).unwrap();
        let snippet = snippet_generator.snippet_from_doc(&doc);
        println!("Document score {}:", score);
        println!(
            "title: {}",
            doc.get_first(title).unwrap().as_text().unwrap()
        );
        println!("snippets: {}", snippet.to_html());        
        println!("custom highlighting: {}", highlight(&snippet));
        //println!("custom highlighting: {}", highlight_color(&snippet));
    } 
}

fn highlight(snippet: &Snippet) -> String {

    let mut result = String::new();
    let mut start_from = 0;
    let max_len = snippet.fragment().as_bytes().len();
    for fragment_range in snippet.highlighted() {        
        if start_from <= fragment_range.start && fragment_range.start  < max_len {
            result.push_str( &snippet.fragment()[start_from..fragment_range.start]);         
        }        
        result.push_str("<mark>");                        
        result.push_str(&snippet.fragment()[fragment_range.clone()]);       
        result.push_str("<mark>");
        start_from = fragment_range.end;        
    }    
    if start_from < max_len {
        result.push_str( &snippet.fragment()[start_from..]);  
    }
    // println!("{}", result);
    result
}

async fn nodeindex_build_search_for_test() ->  Result<()> {
    let root = PathBuf::from("./search-index");
    if !root.exists(){
        std::fs::create_dir_all(&root)?;
    }
    let docid_code = "1234567890";
    let index = NodeIndex::new(root, "test", 10240000)?;
    let doc = SearchDoc{docid: docid_code.to_string(), fname:"abc.conf".to_string(), content:"hello,world! this is a test".to_string(), score: 0.0 };
    index.add_document(&doc).await?;
    // build_index("1234567891", filename, pathfilename, index)      
    // let (docid, _fname, _body) = index.get_schema_tables();
    // let docid_term = Term::from_field_text(docid, docid_code);    
    if  index.doc_is_exist( docid_code )? {
        println!(" Have been exist!");        
    }    
    
    let sdoc = index.read_doc_by_docid( docid_code)?;
    println!("{:#?}", sdoc);       
 //    
    let shared = std::sync::Arc::new(index);
    let res = node_search(&shared, "hello", "term", 1).await?;
    println!("{:#?}", res);       

    Ok(())
}
#[test]
pub fn test_nodeindex_search() {
    let f = nodeindex_build_search_for_test();
    let rt = tokio::runtime::Runtime::new().unwrap();
    let r = rt.block_on(f);
    println!("{:#?}", r);
}

#[test]
pub fn string_width_format_left() {
    let s = format!("{: >10}", "hello");
    println!("length={}, content={:#?}", s.len(), s);
    println!("{:#?}", s.trim())
}


#[test]
pub fn string_width_format_right() {
    let s = format!("{: <10}", "hello");
    println!("length={}, content={:#?}", s.len(), s);
    println!("{:#?}", s.trim())
}